package com.stock.comm.sso.cookie;


import com.stock.comm.sso.cons.Const;
import com.stock.comm.sso.cons.ParamConst;
import com.stock.comm.sso.domain.SsoException;
import com.stock.comm.sso.domain.Ticket;
import com.stock.comm.sso.domain.Token;
import com.stock.comm.sso.domain.TokenBase;
import com.stock.comm.sso.encrypt.AES;
import com.stock.comm.sso.encrypt.SSOEncrypt;
import com.stock.comm.util.RandomDataUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieHelper {

    private static final Logger logger = LoggerFactory.getLogger(CookieHelper.class);

    /* 立即删除 */
    public final static int CLEAR_IMMEDIATELY_REMOVE = 0;

    /**
     * <p>
     * 根据cookieName获取Cookie
     * </p>
     *
     * @param request
     * @param cookieName Cookie name
     * @return Cookie
     */
    public static Cookie findCookieByName(HttpServletRequest request, String cookieName) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null)
            return null;
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals(cookieName)) {
                return cookies[i];
            }
        }
        return null;
    }

    /**
     * <p>
     * 根据cookieName清除指定Cookie
     * </p>
     *
     * @param request
     * @param response
     * @param cookieName cookie name
     * @param domain     Cookie所在的域
     * @param path       Cookie 路径
     * @return boolean
     */
    public static boolean clearCookieByName(HttpServletRequest request, HttpServletResponse response, String cookieName,
                                            String domain, String path) {
        boolean result = false;
        Cookie ck = findCookieByName(request, cookieName);
        if (ck != null) {
            result = clearCookie(response, cookieName, domain, path);
        }
        return result;
    }

    /**
     * <p>
     * 清除指定Cookie 等同于 clearCookieByName(...)
     * </p>
     * <p>
     * <p>
     * 该方法不判断Cookie是否存在,因此不对外暴露防止Cookie不存在异常.
     * </p>
     *
     * @param response
     * @param cookieName cookie name
     * @param domain     Cookie所在的域
     * @param path       Cookie 路径
     * @return boolean
     */
    private static boolean clearCookie(HttpServletResponse response, String cookieName, String domain, String path) {
        boolean result = false;
        try {
            Cookie cookie = new Cookie(cookieName, "");
            cookie.setMaxAge(CLEAR_IMMEDIATELY_REMOVE);
            cookie.setDomain(domain);
            cookie.setPath(path);
            response.addCookie(cookie);
            logger.info("clear cookie " + cookieName);
            result = true;
        } catch (Exception e) {
            logger.error("clear cookie " + cookieName + " is exception!\n" + e.toString());
        }
        return result;
    }

    /**
     * <p>
     * 当前访问域下设置登录Cookie
     * </p>
     *
     * @param request
     * @param response
     * @param encrypt  对称加密算法类
     */
    public static void setSSOCookie(HttpServletRequest request, HttpServletResponse response, String domain,
                                    Token token, SSOEncrypt encrypt) {
        if (encrypt == null) {
            throw new SsoException(" Encrypt not for null.");
        }
        try {
            /**
             * 设置加密 Cookie
             */
            Cookie ck = generateSsoCookie(request, domain, token, encrypt);
            addHttpOnlyCookie(response, ck);
            /**
             * Cookie设置HttpOnly
             */
            // if (config.getCookieHttponly()) {
            // CookieHelper.addHttpOnlyCookie(response, ck);
            // } else {
            response.addCookie(ck);
            // }
        } catch (Exception e) {
            logger.error("set HTTPOnly cookie createAUID is exception! " + e.toString());
        }
    }

    /**
     * <p>
     * 根据Token生成登录信息Cookie
     * </p>
     *
     * @param request
     * @param token   SSO 登录信息票据
     * @param encrypt 对称加密算法类
     * @return Cookie 登录信息Cookie {@link Cookie}
     */
    public static Cookie generateSsoCookie(HttpServletRequest request, String domain, Token token, SSOEncrypt encrypt) {
        try {
            Cookie cookie = new Cookie(Const.cookieName, encryptCookie(request, token, encrypt));
            cookie.setPath(Const.cookie_path);
            // 只允许http传输
            cookie.setSecure(false);
            /**
             * domain 提示
             * <p>
             * 有些浏览器 localhost 无法设置 cookie
             * </p>
             */
            // String domain = ParamConst.cookie_domain;
//            cookie.setDomain(domain);
            if ("".equals(domain) || domain.contains("localhost")) {
                logger.info("if you can't login, please enter normal domain. instead:" + domain);
            }

            /**
             * 设置Cookie超时时间
             */
            int maxAge = Const.COOKIE_MAX_AGE;
            Integer attrMaxAge = (Integer) request.getAttribute(Const.SSO_COOKIE_MAXAGE);
            if (attrMaxAge != null) {
                maxAge = attrMaxAge;
            }
            if (maxAge >= 0) {
                cookie.setMaxAge(maxAge);
//                cookie.setMaxAge(Integer.MAX_VALUE);
            }
            return cookie;
        } catch (Exception e) {
            logger.info("generateCookie is exception!" + e.toString());
            return null;
        }
    }

    /**
     * <p>
     * 加密Token信息
     * </p>
     *
     * @param request
     * @param token   SSO 登录信息票据
     * @param encrypt 对称加密算法类
     * @return Cookie 登录信息Cookie {@link Cookie}
     */
    public static String encryptCookie(HttpServletRequest request, TokenBase token, SSOEncrypt encrypt)
            throws Exception {
        if (token == null) {
            throw new SsoException(" Token not for null.");
        }
        /**
         * token加密混淆
         */
        String jt = token.jsonToken();
        StringBuffer sf = new StringBuffer();
        sf.append(jt);
        sf.append(Const.CUT_SYMBOL);
        sf.append(RandomDataUtil.getCharacterAndNumber(8));
        return encrypt.encrypt(sf.toString(), ParamConst.AES_KEY);
    }

    /**
     * <p>
     * 添加 Cookie
     * </p>
     *
     * @param response
     * @param domain   所在域
     * @param path     域名路径
     * @param name     名称
     * @param tic      内容
     * @param maxAge   生命周期参数
     * @param httpOnly 只读
     * @param secured  Https协议下安全传输
     */
    public static void addCookie(HttpServletRequest request, HttpServletResponse response, String domain, String path,
                                 String name, Ticket tic, int maxAge, boolean httpOnly, boolean secured) {
        try {
            String value = encryptCookie(request, tic, AES.getInstance());
            Cookie cookie = new Cookie(name, value);
            /**
             * 不设置该参数默认 当前所在域
             */
            if (domain != null && !"".equals(domain)) {
                cookie.setDomain(domain);
            }
            cookie.setPath(path);
            cookie.setMaxAge(maxAge);

            /** Cookie 只在Https协议下传输设置 */
            if (secured) {
                cookie.setSecure(secured);
            }

            /** Cookie 只读设置 */
            if (httpOnly) {
                addHttpOnlyCookie(response, cookie);
            } else {
                /*
				 * //servlet 3.0 support cookie.setHttpOnly(httpOnly);
				 */
                response.addCookie(cookie);
            }
        } catch (Exception e) {
            logger.error("AuthToken encryptCookie error.\n" + e.toString());
        }
    }

    /**
     * <p>
     * 解决 servlet 3.0 以下版本不支持 HttpOnly
     * </p>
     *
     * @param response HttpServletResponse类型的响应
     * @param cookie   要设置httpOnly的cookie对象
     */
    public static void addHttpOnlyCookie(HttpServletResponse response, Cookie cookie) {
        if (cookie == null) {
            return;
        }
        /**
         * 依次取得cookie中的名称、值、 最大生存时间、路径、域和是否为安全协议信息
         */
        String cookieName = cookie.getName();
        String cookieValue = cookie.getValue();
        int maxAge = cookie.getMaxAge();
        String path = cookie.getPath();
        String domain = cookie.getDomain();
        boolean isSecure = cookie.getSecure();
        StringBuffer sf = new StringBuffer();
        sf.append(cookieName + "=" + cookieValue + ";");

        if (maxAge >= 0) {
            sf.append("Max-Age=" + cookie.getMaxAge() + ";");
        }

        if (domain != null) {
            sf.append("domain=" + domain + ";");
        }

        if (path != null) {
            sf.append("path=" + path + ";");
        }

        if (isSecure) {
            sf.append("secure;HTTPOnly;");
        } else {
            sf.append("HTTPOnly;");
        }

        response.addHeader("Set-Cookie", sf.toString());
    }
}
